package hydrap2p.library;

import hydrap2p.ServiceLocation;
import hydrap2p.helper.ServiceConnector;
import hydrap2p.helper.ThriftServerThread;
import hydrap2p.helper.ThriftUtils;
import hydrap2p.helper.Utility;
import hydrap2p.library.listening.ListenerManager;
import hydrap2p.logging.Level;
import hydrap2p.modulemanager.ModuleControl;
import hydrap2p.servicemanager.NameTaken;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;

import org.apache.thrift.TException;

public class Server implements Library.Iface, ModuleControl.Iface {
	private Data data;
	private SharedFolders folders;
	
	private ThriftServerThread server;
	private ThriftServerThread controlserver;
	
	private Timer timer;
	private static final long pingerdelay = 10000;
	
	public static void main(String[] args) {
		Server server=null;
		try {
			server=new Server();
			server.run();
		} catch (Exception e) {
			System.err.println(e.getLocalizedMessage());
		}
		if (server==null)
			server.shutdown();
	}
	
	public Server() throws Exception {
		Database database=Database.getInstance("jdbc:hsqldb:file:"+Utility.appendPath(Utility.getUserConfigurationPath(), "library.db"), "SA", "");
		if (!database.isInited())
			database.initDB();
		data=Data.getInstance();
		folders=new SharedFolders();
	}
	
	public void run() throws IOException, NameTaken
	{
		controlserver=ThriftUtils.initThriftServer(new ModuleControl.Processor(this), "LibraryCTL", 8503);
		controlserver.start();
		server=ThriftUtils.initThriftServer(new Library.Processor(this), "Library", 8502);
		server.start();
		
		timer = new Timer();
		timer.schedule(new PingTimer(), pingerdelay, pingerdelay);
	}
	
	private class PingTimer extends TimerTask {

		@Override
		public void run() {
			try {
				ServiceConnector.getServiceManager().ping();
			} catch (Exception e) {
				shutdown();
			}
		}
	}
	
	private void shutdown()
	{
		if (server!=null)
			server.shutdown();
		if (controlserver!=null)
			controlserver.shutdown();
		if (timer!=null)
			timer.cancel();
		System.exit(0);
	}
	
	@Override
	public long addSharedFolder(String arg0) throws TException, FileNotFound {
		try {
			return folders.addSharedFolder(arg0);
		} catch (FileNotFoundException e) {
			throw new FileNotFound(arg0);
		}
	}

	@Override
	public void rescan(long arg0) throws TException {
		folders.rescan(arg0,true);
	}

	@Override
	public boolean removeSharedFolder(long arg0) throws TException {
		folders.deleteSharedFolder(arg0);
		return true;
	}

	@Override
	public long createFile(String arg0, long arg1) throws TException,FileAlreadyExists,IOProblem {
		try {
			return data.createFile(arg0, arg1);
		} catch (IOException e) {
			throw new IOProblem(arg0,"Error while creating file: "+e.getLocalizedMessage());
		}
	}

	@Override
	public long createFileSet(String name, List<Long> arg0) throws InvalidUID, TException {
		return data.createFileSet(name,arg0);
	}

	@Override
	public SharedItemInfo getSharedItemInfo(long arg0) throws InvalidUID, TException {
		SharedItemInfo ret=data.getSharedItemInfo(arg0);
		if (ret==null)
			throw new InvalidUID(arg0);
		return ret;
	}

	@Override
	public List<SharedItemInfo> getChildren(long arg0) throws TException {
		return data.getChildren(arg0);
	}

	@Override
	public List<Range> getDownloadedRanges(long arg0) throws TException {
		return data.getDownloadedRanges(arg0);
	}

	@Override
	public List<Range> getVerifiedRanges(long arg0) throws TException {
		return data.getVerifiedRanges(arg0);
	}

	@Override
	public boolean saveData(long arg0, long arg1, ByteBuffer arg2)
			throws TException, FileNotFound, IOProblem, InvalidUID {
		try {
			return data.saveData(arg0, arg1, arg2.array(), 0, arg2.capacity());
		} catch (FileNotFoundException e) {
			SharedItemInfo temp=data.getSharedItemInfo(arg0);
			if (temp!=null)
			{
				LibraryLogging.log(Level.ERROR, "Library (Server)", "Attempt to save data to an invalid file: "+Utility.appendPath(temp.pathname, temp.name)+" full message: "+e.getLocalizedMessage());
				throw new FileNotFound(Utility.appendPath(temp.pathname, temp.name));
			}
		} catch (IOException e) {
			SharedItemInfo temp=data.getSharedItemInfo(arg0);
			if (temp!=null)
			{
				LibraryLogging.log(Level.ERROR, "Library (Server)", "IOException while saving file: "+Utility.appendPath(temp.pathname, temp.name)+" full message: "+e.getLocalizedMessage());
				throw new IOProblem(Utility.appendPath(temp.pathname, temp.name),e.getLocalizedMessage());
			}
			else
				LibraryLogging.log(Level.ERROR, "Library (Server)", "IOException while saving unknown file: uid: "+arg0+" full message: "+e.getLocalizedMessage());
		}
		return false;
	}

	@Override
	public ByteBuffer loadData(long arg0, long arg1, int len) throws TException, FileNotFound, IOProblem {
		try {
			return ByteBuffer.wrap(data.loadData(arg0, arg1, len));
		} catch (FileNotFoundException e) {
			SharedItemInfo temp=data.getSharedItemInfo(arg0);
			if (temp!=null)
			{
				LibraryLogging.log(Level.ERROR, "Library (Server)", "Attempt to read data from an invalid file: "+Utility.appendPath(temp.pathname, temp.name)+" full message: "+e.getLocalizedMessage());
				throw new FileNotFound(Utility.appendPath(temp.pathname, temp.name));
			}
		} catch (IOException e) {
			SharedItemInfo temp=data.getSharedItemInfo(arg0);
			if (temp!=null)
			{
				LibraryLogging.log(Level.ERROR, "Library (Server)", "IOException while reading file: "+Utility.appendPath(temp.pathname, temp.name)+" full message: "+e.getLocalizedMessage());
				throw new IOProblem(Utility.appendPath(temp.pathname, temp.name),e.getLocalizedMessage());
			}
			else
				LibraryLogging.log(Level.ERROR, "Library (Server)", "IOException while reading unknown file: uid: "+arg0+" full message: "+e.getLocalizedMessage());
			
		}
		return null;
	}

	@Override
	public boolean registerListener(ServiceLocation arg0,
			LibrarySubscriptionType arg1) throws TException {
		return ListenerManager.getInstance().subscribe(arg0, arg1);
	}

	@Override
	public boolean unregisterListener(ServiceLocation arg0) throws TException {
		ListenerManager.getInstance().unsubscribe(arg0);
		return true;
	}

	@Override
	public boolean registerHashGenerator(String arg0, ServiceLocation arg1)
			throws TException {
		return Hashers.getInstance().subscribe(arg0, arg1);
	}

	@Override
	public boolean registerMetadataLoader(String arg0, ServiceLocation arg1)
			throws TException {
		return Metadaters.getInstance().subscribe(arg0, arg1);
	}

	@Override
	public boolean unregisterHashGenerator(String arg0, ServiceLocation arg1)
			throws TException {
		return Hashers.getInstance().unsubscribe(arg0);
	}

	@Override
	public boolean unregisterMetadataLoader(String arg0, ServiceLocation arg1)
			throws TException {
		return Metadaters.getInstance().unsubscribe(arg0, arg1);
	}

	@Override
	public List<SharedItemInfo> searchFilename(String arg0) throws TException {
		return data.searchFilename(null,arg0);
	}

	@Override
	public List<SharedItemInfo> searchHash(String algorithm, String hashdata)
			throws TException {
		return data.searchHash(algorithm, hashdata);
	}

	@Override
	public void setFileName(long arg0, String arg1) throws FileAlreadyExists {
		data.setFileName(arg0, arg1);
	}

	@Override
	public void setPathName(long arg0, String arg1) throws FileAlreadyExists {
		data.setPathName(arg0, arg1);
	}

	@Override
	public boolean setShared(long arg0, boolean arg1) throws TException {
		data.setShared(arg0, arg1);
		return arg1;
	}

	@Override
	public void setDownloadHash(long arg0, HashData arg1) throws TException {
		data.setDownloadHash(arg0, arg1);
	}

	@Override
	public void deleteDownloadHash(long arg0, HashData arg1) throws TException {
		data.deleteDownloadHash(arg0, arg1);		
	}

	@Override
	public boolean setMetadata(long arg0, String arg1, String arg2)
			throws TException {
		return data.setMetadata(arg0, arg1, arg2);
	}

	@Override
	public boolean deleteMetadata(long arg0, String arg1) throws TException {
		return data.deleteMetadata(arg0, arg1);
	}
	
	@Override
	public void setExtraData(long arg0, String arg1, String arg2)
			throws TException {
		data.setExtraData(arg0, arg1, arg2);		
	}

	@Override
	public boolean deleteExtraData(long arg0, String arg1) throws TException {
		return data.deleteExtraData(arg0, arg1);
	}

	@Override
	public void ping() throws TException {
		return;
	}

	@Override
	public List<String> supportedInterfaces() throws TException {
		return Arrays.asList("Library", "Service", "ModuleControl");
	}

	@Override
	public void stop() {
		shutdown();
	}

}
